#include "RSA.h"
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "../Entidades/Utils.h"
using namespace Encriptacion;
using namespace Entidades;

RSA::RSA() {
}

string RSA::generarParDeClaves(string nombreDelAdmin) {

	Configuracion* config = Configuracion::getInstancia();
	int longitudCLave = config->getLongitudClaveRSA();

	// se generan 2 numeros enteros largos, primos y aleatorios
	NumerosPrimos* numerosPrimos = new NumerosPrimos();
	unsigned long long p = 0;
	unsigned long long q = 0;
	numerosPrimos->getParNumerosPrimos(p, q, longitudCLave);
	delete numerosPrimos;

	// luego se calcula n = (p * q) y fi = (p - 1) * (q - 1)
	unsigned long long n = (p * q);
	unsigned long long fi = (p - 1) * (q - 1);

	// selecciono otro número aleatorio 'd', relativamente primo de fi --> MCD(d, fi) = 1
	unsigned long long d = this->obtenerNumeroRelativamentePrimoA(fi);

	// luego, se calcula e * d = 1 MOD (FI) utilizando el 'Algoritmo extendido de Euclides'
	unsigned long long e = this->algoritmoExtendidoDeEuclides(d, fi);
	unsigned long long dia = this->algoritmoExtendidoDeEuclides(e,fi);

	// guardamos la clave privada (d,n)
	ClaveRSA* clavePrivada = new ClaveRSA(nombreDelAdmin, Encriptacion::Privada, d, n);
	clavePrivada->guardarClave();
	delete clavePrivada;

	// guardamos la clave pública (e,n)
	ClaveRSA* clavePublica = new ClaveRSA(nombreDelAdmin, Encriptacion::Publica, e, n);
	clavePublica->guardarClave();
	delete clavePublica;

	// retornamos el folder que contiene a las claves
	return nombreDelAdmin;
}

string RSA::encriptarArchivo(string nombreDelAdmin, string pathDelArchivoOriginal, string entidad) {

	// obtengo la clave publica del admin que va recibir el mensaje
	ClaveRSA* claveParaEncriptar = new ClaveRSA(nombreDelAdmin, Encriptacion::Publica);
	Configuracion* config = Configuracion::getInstancia();
	string pathDelArchivoEncriptado = config->getPathAdministradores()+ nombreDelAdmin + "/Mensajes/Encriptado"+ entidad +".txt";

	bool pudoEncriptar = this->generarEncriptacion(pathDelArchivoOriginal, pathDelArchivoEncriptado, claveParaEncriptar);

	delete claveParaEncriptar;

	if (pudoEncriptar)
		return pathDelArchivoEncriptado;
	else
		return "";
}

string RSA::desencriptarArchivo(string nombreDelAdmin, string nombreDelArchivoEncriptado) {

	// obtengo la clave privada del admin que recibe un mensaje encriptado
	ClaveRSA* claveParaDesencriptar = new ClaveRSA(nombreDelAdmin, Encriptacion::Privada);
	Configuracion* config = Configuracion::getInstancia();
	string pathDelArchivoDesencriptado = config->getPathAdministradores() + nombreDelAdmin + "/Mensajes/Des" + nombreDelArchivoEncriptado;
	string pathDelArchivoEncriptado = config->getPathAdministradores() + nombreDelAdmin + "/Mensajes/" + nombreDelArchivoEncriptado;

	bool pudoEncriptar = this->generarDesencriptacion(pathDelArchivoEncriptado, pathDelArchivoDesencriptado,
			claveParaDesencriptar);

	delete claveParaDesencriptar;

	if (pudoEncriptar)
		return pathDelArchivoDesencriptado;
	else
		return "";
}

string RSA::desencriptarArchivo(ClaveRSA* claveParaDesencriptar, string pathDelArchivoEncriptado,string pathDelArchivoDesencriptado) {

	bool pudoEncriptar = this->generarDesencriptacion(pathDelArchivoEncriptado, pathDelArchivoDesencriptado,
			claveParaDesencriptar);

	delete claveParaDesencriptar;

	if (pudoEncriptar)
		return pathDelArchivoDesencriptado;
	else
		return "";
}

unsigned long long RSA::obtenerMaximoComunDivisor(unsigned long long a, unsigned long long b) {

	if (b == 0)
		return a;
	else
		return obtenerMaximoComunDivisor(b, a % b);
}

/* Devuelve un entero 'x' / x = (base ^ exponente) MOD modulo */
unsigned long long RSA::exponenciacionModular(unsigned long long base, unsigned long long exponente,
		unsigned long long modulo) {

	unsigned long long resultado = 1;
	unsigned long long aux = base % modulo;

	while (exponente > 0) {
		if (exponente % 2 != 0) {
			resultado = (resultado * aux) % modulo;
		}
		aux = (aux * aux) % modulo;
		exponente = exponente / 2;
	}

	return resultado;
}

/* Genera un número random entre 0 u la cotaMaxima pasado por parámetro y chequea si ese
 * random es relativamente primo */
unsigned long long RSA::obtenerNumeroRelativamentePrimoA(unsigned long long numero) {

	unsigned long long toReturn = 0;
	unsigned long long cotaMaxima = numero;

	// TODO: fijo la cota máxima en 50.000 para que no sea muy grande
	if (cotaMaxima > 50000)
		cotaMaxima = 50000;

	// inicializo la semilla del número random
	srand(time(NULL));

	do {

		// genero un número aletario entre 0 y cotaMaxima
		toReturn = rand() % numero + 1;

	} while (this->obtenerMaximoComunDivisor(numero, toReturn) != 1);

	return toReturn;
}

/* Buscamos un e / e = ((i*modulo)+1)/ numero, con e entero (le aplicamos el módulo a la división y tiene q dar cero)
 * e i = 1, 2, 3, ...
 */
unsigned long long RSA::algoritmoExtendidoDeEuclides(unsigned long long numero, unsigned long long modulo) {

	unsigned long long resultado = 0;
	unsigned int i = 0;
	bool termine = false;

	while (!termine) {

		i++;
		resultado = ((i * modulo) + 1) / numero;
		termine = (((i * modulo) + 1) % numero) == 0;
	}

	return resultado;
}

bool RSA::abrirArchivo(fstream& archivo, string path, bool crearNuevo) {

	if (crearNuevo)
		archivo.open(path.c_str(), ios::in | ios::out | ios::trunc);
	else
		archivo.open(path.c_str(), ios::in | ios::out);

	if (archivo.bad()) {
		archivo.open(path.c_str(), ios::in | ios::out);
	}

	return (!archivo.bad());
}

bool RSA::generarEncriptacion(string pathDelArchivoOriginal, string pathDelArchivoResultado, ClaveRSA* clave) {

	bool termine = false;

	// abrimos el archivo original
	fstream archivoAEncriptar;
	this->abrirArchivo(archivoAEncriptar, pathDelArchivoOriginal, false);

	// creamos y abrimos el archivo resultado
	fstream archivoEncriptado;
	this->abrirArchivo(archivoEncriptado, pathDelArchivoResultado, true);

	// separo el texto en bloques de numero de digitos de n - 1
	unsigned int tamanioDelBloque = Utils::convertirEnteroAString(clave->getModulo()).length() - 1;

	while (!termine) {

		char caracterLeido = NULL;
		string valorAGuardar = "";

		archivoAEncriptar.get(caracterLeido);

		// aplico la codificacion
		unsigned long long resultado = this->exponenciacionModular((unsigned int) caracterLeido, clave->getPotencia(),
				clave->getModulo());

		// fuerzo la clave a tener 'tamanioDelBloque' caracteres
		string temp = Utils::convertirEnteroAString(resultado);
		for (unsigned int i = 0; i < tamanioDelBloque + 1 - temp.length(); i++) {
			valorAGuardar += '0';
		}
		valorAGuardar += temp;

		if (resultado != 0)
			archivoEncriptado << valorAGuardar;

		// termino si llegué al EOF
		termine = archivoAEncriptar.eof();
	}

	archivoAEncriptar.close();
	archivoEncriptado.close();

	return termine;
}

bool RSA::generarDesencriptacion(string pathDelArchivoOriginal, string pathDelArchivoResultado, ClaveRSA* clave) {

	bool termine = false;

	// abrimos el archivo original
	fstream archivoADesencriptar;
	this->abrirArchivo(archivoADesencriptar, pathDelArchivoOriginal, false);

	// creamos y abrimos el archivo resultado
	fstream archivoDesencriptado;
	this->abrirArchivo(archivoDesencriptado, pathDelArchivoResultado, true);

	// separo el texto en bloques de numero de digitos de n
	unsigned int tamanioDelBloque = Utils::convertirEnteroAString(clave->getModulo()).length();

	while (!termine) {

		unsigned int caracteresLeidos = 0;
		string valorLeido = "";

		// leo hasta q sea EOF o hasta que supere el rango
		do {

			char caracterLeido = NULL;
			archivoADesencriptar.get(caracterLeido);

			// en este caso leo números, digito a digito
			valorLeido += caracterLeido;
			caracteresLeidos++;
		}while (!archivoADesencriptar.eof() && caracteresLeidos < tamanioDelBloque);

		// aplico la codificacion
		unsigned long long numeroACodificar = atoi(valorLeido.c_str());
		unsigned long long resultado = this->exponenciacionModular(numeroACodificar, clave->getPotencia(),
				clave->getModulo());

		if (resultado != 0)
			archivoDesencriptado << (char) resultado;

		// termino si llegué al EOF
		termine = archivoADesencriptar.eof();
	}

	archivoADesencriptar.close();
	archivoDesencriptado.close();

	return termine;
}

RSA::~RSA() {
}
